package com.zxy.supplier_system.server.service

import com.zxy.supplier_system.server.configuration.AUTHORIZATION_HEADER_VALUE_PREFIX
import com.zxy.supplier_system.server.configuration.JwtProperties
import com.zxy.supplier_system.server.controller.AuthorizationController
import com.zxy.supplier_system.server.entity.Employee
import com.zxy.supplier_system.server.entity.JwtTokenMetadata
import com.zxy.supplier_system.server.entity.WechatUserInfo
import com.zxy.supplier_system.server.repository.EmployeeRepository
import com.zxy.supplier_system.server.repository.SystemServiceRepository
import com.zxy.supplier_system.server.repository.WechatUserInfoRepository
import com.zxy.supplier_system.server.utils.generateToken
import com.zxy.supplier_system.server.utils.orElse404
import com.zxy.supplier_system.server.utils.parseToClaims
import com.zxy.supplier_system.server.utils.wechat.mini.WechatMiniProgramHelper
import com.zxy.supplier_system.server.utils.wechat.mini.code2Session
import io.jsonwebtoken.ExpiredJwtException
import jakarta.servlet.http.HttpServletRequest
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import java.time.OffsetDateTime

@Service
class AuthorizationService(
    private val employeeRepository: EmployeeRepository,
    private val jwtProperties: JwtProperties,
    private val employeeService: EmployeeService,
    private val systemServiceRepository: SystemServiceRepository,
    private val wechatMiniProgramHelper: WechatMiniProgramHelper,
    private val wechatUserInfoRepository: WechatUserInfoRepository,
) {

    companion object {
        const val JWT_CLAIM_CLIENT_TYPE_KEY = "CLIENT_TYPE"
    }

    @Transactional
    fun logInByPhoneNumberAndPassword(
        phoneNumber: String,
        password: String,
        client: JwtTokenMetadata.Client,
        enterpriseCode: String,
    ): ResponseEntity<String> {
        val user = employeeRepository.findByPhoneNumberAndPasswordAndSystemService(
            phoneNumber,
            password,
            systemServiceRepository.findByIdOrNull(enterpriseCode).orElse404(),
        ).orElse404()
        checkEmployeeStatus(user)
        return logInAndGenerateTokenResponseEntity(user, client)
    }

    private fun logInAndGenerateTokenResponseEntity(
        user: Employee,
        client: JwtTokenMetadata.Client
    ): ResponseEntity<String> {
        val jwtTokenMetadata = user.jwtTokenMetadataList.find {
            it.client == client
        } ?: JwtTokenMetadata().apply {
            this.client = client
            this.user = user
            user.jwtTokenMetadataList.add(this)
        }

        jwtTokenMetadata.lastLogInDateTime = OffsetDateTime.now()
        this.employeeRepository.save(user)

        return generateUserTokenResponseEntity(user, client)
    }

    private fun generateUserTokenResponseEntity(
        user: Employee,
        client: JwtTokenMetadata.Client
    ): ResponseEntity<String> {
        val token = generateToken(
            user.username,
            jwtProperties.secret,
            mapOf(JWT_CLAIM_CLIENT_TYPE_KEY to client)
        )
        val authorizationHeader = AUTHORIZATION_HEADER_VALUE_PREFIX + token
        return ResponseEntity.status(HttpStatus.OK)
            .header(HttpHeaders.AUTHORIZATION, authorizationHeader)
            .body(authorizationHeader)
    }

    @Transactional
    fun logOut(request: HttpServletRequest) {
        val header =
            request.getHeader(HttpHeaders.AUTHORIZATION) ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
        val token = header.substring(AUTHORIZATION_HEADER_VALUE_PREFIX.length)
        val claims = try {
            parseToClaims(token, jwtProperties.secret)
        } catch (e: ExpiredJwtException) {
            throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
        }
        val client =
            JwtTokenMetadata.Client.valueOf(claims.get(JWT_CLAIM_CLIENT_TYPE_KEY, String::class.java) ?: return)
        val employee = this.employeeService.getRequiredCurrentEmployee()
        val jwtTokenMetadata = employee.jwtTokenMetadataList.find {
            it.client == client
        } ?: return
        jwtTokenMetadata.lastLogOutDateTime = OffsetDateTime.now()
        this.employeeRepository.save(employee)
    }

    @Transactional
    fun updatePassword(request: AuthorizationController.UpdatePasswordRequest) {
        val employee = this.employeeService.getRequiredCurrentEmployee()
        if (employee.password == request.password) {
            employee.password = request.newPassword
            this.employeeRepository.save(employee)
        } else {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "INVALID_PASSWORD")
        }
    }

    @Transactional
    fun logInByWechatCode(wechatCode: String, enterpriseCode: String): ResponseEntity<String> {
        val code2SessionResponse = wechatMiniProgramHelper.code2Session(wechatCode)
        val employee = employeeRepository.findByWechatUserInfo_MiniProgramOpenIdAndSystemService_Id(code2SessionResponse.openid!!,enterpriseCode).orElse404()
        checkEmployeeStatus(employee)
        return logInAndGenerateTokenResponseEntity(employee, JwtTokenMetadata.Client.MINI_PROGRAM)
    }

    @Transactional
    fun logInByWechatOpenIdAndPhoneNumber(
        wechatCode: String,
        phoneNumber: String,
        password: String,
        enterpriseCode: String
    ): ResponseEntity<String> {
        val user = employeeRepository.findByPhoneNumberAndPasswordAndSystemService(
            phoneNumber,
            password,
            systemServiceRepository.findByIdOrNull(enterpriseCode).orElse404(),
        ).orElse404()
        checkEmployeeStatus(user)

        val code2SessionResponse = wechatMiniProgramHelper.code2Session(wechatCode)
        if (user.wechatUserInfo == null) {
            user.wechatUserInfo =
                (if (code2SessionResponse.unionid == null || code2SessionResponse.openid == null) null else wechatUserInfoRepository.findByUnionIdOrMiniProgramOpenId(
                    code2SessionResponse.unionid, code2SessionResponse.openid
                )) ?: WechatUserInfo()
        }
        user.wechatUserInfo!!.miniProgramOpenId = code2SessionResponse.openid
        user.wechatUserInfo!!.unionId = code2SessionResponse.unionid
        return logInAndGenerateTokenResponseEntity(user, JwtTokenMetadata.Client.MINI_PROGRAM)
    }

    private fun checkEmployeeStatus(employee: Employee){
        if (!employee.isEnabled){
            throw ResponseStatusException(HttpStatus.FORBIDDEN,"DISABLED")
        }
    }

}
